#!/bin/sh
#
# Copyright (c) 2006 The NetBSD Foundation, Inc.
# All rights reserved.
#
# This code is derived from software contributed to The NetBSD Foundation
# by Johnny C. Lam.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#        This product includes software developed by the NetBSD
#        Foundation, Inc. and its contributors.
# 4. Neither the name of The NetBSD Foundation nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# From $NetBSD: fetch,v 1.6 2006/08/03 14:02:58 jlam Exp $
#

######################################################################
#
# NAME
#	fetch -- fetch files via URLs
#
# SYNOPSIS
#	fetch [-m archive|cvs|git|svn] [-e extract_dir] [-c] [-d dir]
#		[-f distinfo] file site ..."
#
# DESCRIPTION
#	fetch will attempt to fetch the file from the list of specified
#	sites in the order given.  The complete URL to the file on each
#	site should be the concatenation of the specified site and file.
#	If the file cannot be fetched successfully, then we try the next
#	listed site.
#
#	If the file already exists on the disk and is verified, then
#	no fetch action is taken.
#
# OPTIONS
#	-m	Fetch method. Must be archive, cvs, git or svn.
#
#	-e	Extract directory.
#
#	-c	Verify the checksum for the file.  If the checksum
#		does not match, then the fetch is determined to be
#		not successful.
#
#	-d dir	Fetch the files into the specified directory.
#
#	-f distinfo
#		The path to the distinfo file containing the checksums
#		for the file.  The file format should match what is
#		needed by the pkgsrc/mk/checksum/checksum script.
#
#	-r	Resume a previous fetch for the file.  In this case,
#		the file is first saved to a ".pkgsrc.resume" file,
#		and is later renamed to the final file name if the
#		complete file has been sucessfully fetched.
#
#	-v	Show the actual command line used to fetch the file
#		from each site.
#
# ENVIRONMENT
#	ROBOTPKG_DIR	This is a hint to help locate the default
#			checksum script.
#
#	CHECKSUM	This is the path to the checksum script used
#			when "-c" is specified.
#
#	FETCH_CMD	This is the actual command used for transferring
#			files from the various sites.
#
#	The following are lists of options to pass to ${FETCH_CMD}:
#
#	FETCH_BEFORE_ARGS
#			These options appear before all other options.
#
#	FETCH_AFTER_ARGS
#			These options appear after all other options.
#
#	FETCH_RESUME_ARGS
#			These options appear just after FETCH_BEFORE_ARGS
#			options and cause ${FETCH_CMD} to resume a
#			previous file transfer.
#
#	FETCH_OUTPUT_ARGS
#			These options specify the name of the local file
#			that will hold the contents of the fetched file.
#
######################################################################

: ${ROBOTPKG_DIR:=/usr/robotpkg}
: ${CHECKSUM:=${ROBOTPKG_DIR}/mk/checksum/checksum}
: ${CP:=cp}
: ${ID:=id}
: ${UMASK:=umask}
: ${ECHO:=echo}
: ${FETCH_CMD:=tnftp}
: ${MKDIR:=mkdir}
: ${MV:=mv}
: ${TEST:=test}
: ${TOUCH:=touch}
: ${WC:=wc}
: ${FIND:=find}
: ${SORT:=sort}
: ${GZIP:=gzip}
: ${PAX:=pax}

self="${0##*/}"

usage() {
    ${ECHO} 1>&2	\
	"usage: $self [-m archive|cvs|git|mercurial|svn]"	\
	" [-e extract_dir] [-c] [-f distinfo] [-r] file site ..."
}

# Process optional arguments
format=archive
extract=work
checksum=
distinfo=
fetchdir=.
resume=
verbose=
while ${TEST} $# -gt 0; do
    case "$1" in
	-m)	format="$2"; shift 2 ;;
	-e)	extract="$2"; shift 2 ;;
	-c)	checksum=yes; shift ;;
	-d)	fetchdir="$2"; shift 2 ;;
	-f)	distinfo="$2"; shift 2 ;;
	-r)	resume=yes; shift ;;
	-v)	verbose=yes; shift ;;
	--)	shift; break ;;
	-*)	${ECHO} 1>&2 "$self: unknown option -- ${1#-}"
		usage
		exit 1
		;;
	*)	break ;;
    esac
done
if ${TEST} -n "$checksum" -a -z "$distinfo"; then
	${ECHO} 1>&2 "$self: \`\`-c'' requires \`\`-f distinfo''."
	exit 1
fi
if ${TEST} -n "$resume"; then
	if ${TEST} -z "$distinfo"; then
		${ECHO} 1>&2 "$self: \`\`-r'' requires \`\`-f distinfo''."
		resume=
	elif ${TEST} "x${FETCH_RESUME_ARGS}" = "x"; then
		${ECHO} 1>&2 "$self: \`\`-r'' requires FETCH_RESUME_ARGS to be non-empty."
		resume=
	fi
	${TEST} -n "$resume" ||
		${ECHO} 1>&2 "$self: Falling back to non-resume fetch."
fi

# Process required arguments
if ${TEST} $# -lt 2; then
	usage
	exit 1
fi
file="$1"; shift
path="$fetchdir/$file"

if ${TEST} -n "$distinfo" && ${TEST} ! -f "$distinfo"; then
	${ECHO} 1>&2 "$self: distinfo file missing: $distinfo"
	exit 1
fi

${TEST} -d $fetchdir || ${MKDIR} -p $fetchdir 2>/dev/null
if ${TEST} ! -w $fetchdir; then
	${ECHO} 1>&2 "$self: Cannot write to $fetchdir"
	exit 1
fi

# Compute the expected size of the fetched file.
distsize=
distunits=
if ${TEST} -n "$distinfo"; then
	while read d_type d_file d_equals d_size d_units; do
		case "$d_type" in
		Size)	;;	# only handle "Size" lines
		*)	continue ;;
		esac
		case "$fetchdir" in
		".")	${TEST} "$d_file" = "($file)" || continue ;;
		*)	${TEST} "$d_file" = "($path)" || continue ;;
		esac
		distsize="$d_size"; distunits="$d_units"
		break
	done < $distinfo
fi

# verify_file $file
#	If we can checksum the file, then see if it matches the listed
#	checksums in the distinfo file.  If we can check the size, then
#	check that instead.  We strip off ".pkgsrc.resume" from the
#	filename so that we can verify the checksum for the temporary
#	fetch file as well.
#
verify_file() {
	_file="${1#./}"
	${TEST} -f $_file || return 1
	if ${TEST} -n "$checksum"; then
		eval ${CHECKSUM} -s ".pkgsrc.resume" '$distinfo' '${_file}' || return 1
		return 0
	elif ${TEST} -n "$distsize"; then
		_size=`${WC} -c < $_file`
		${TEST} "$_size" -eq "$distsize" || return 1
		return 0
	fi
	return 0;
}

# If the file already exists and it verifies, then we don't need to fetch
# it again.
#
if verify_file $path; then
	exit 0
fi

# Set the name of the output file.  In the "resume" case, we initialize
# the fetch loop by ensuring that the temporary output file already
# exists.
#
outputfile="$file"
outputpath="$fetchdir/$outputfile"
if ${TEST} -n "$resume"; then
	outputfile="${file}.pkgsrc.resume"
	outputpath="$fetchdir/$outputfile"
	if ${TEST} ! -f $outputpath; then
		if ${TEST} -f $path; then
			${CP} -f $path $outputpath
		else
			${TOUCH} $outputpath
		fi
	fi
	#
	# If the temporary file verifies, then we don't need to resume
	# fetching it.
	#
	if verify_file $outputpath; then
		${MV} -f $outputpath $path
		exit 0
	fi
	size=`${WC} -c < $outputpath`
	${ECHO} "=> Downloaded size: $size bytes"
fi
${TEST} -z "$distsize" || ${ECHO} "=> Total size: $distsize $distunits"

# Iterate over each site and try to fetch the file.  We verify the fetched
# file to see if we need to try fetching from the next site.
#
while ${TEST} $# -gt 0; do
	site="$1"; shift

	# Build repository bits from repository@rev+module format
	repo=${site%@*}
	rev=${site##*@}
	${TEST} "$rev" = "$site" && rev=
	module=${rev##*+}
	rev=${rev%+*}
	${TEST} "$module" = "$rev" && module=

	(
	    cd $fetchdir
	    fetch_cmd="${FETCH_CMD} ${FETCH_BEFORE_ARGS}"
	    if ${TEST} -n "$resume"; then
		fetch_cmd="${fetch_cmd} ${FETCH_RESUME_ARGS}"
	    fi
	    if ${TEST} -n "${FETCH_OUTPUT_ARGS}"; then
		fetch_cmd="${fetch_cmd} ${FETCH_OUTPUT_ARGS} $outputfile"
	    fi
	    fetch_cmd="${fetch_cmd} ${FETCH_AFTER_ARGS}"

	    # Use the correct extraction procedure to create the archive based
	    # on the repository format.
	    ${TEST} -d "$extract" && rm -rf "$extract"
	    case "$format" in
		custom)
		    ${ECHO} $fetch_cmd $site
		    $fetch_cmd $site
		    ;;

		archive)
		    ${ECHO} $fetch_cmd $site$file
		    $fetch_cmd $site$file
		    ;;

		cvs)
		    ${ECHO} $fetch_cmd -d $repo export -r $rev $module
		    $fetch_cmd -d $repo export -d $extract -r $rev $module &&
		    ${FIND} $extract \! -type d | ${SORT} | ${PAX} -wzf $file
		    ;;

		git)
		    ${ECHO} $fetch_cmd clone $repo $extract
		    export GIT_SSL_NO_VERIFY=1
		    $fetch_cmd clone -n $repo $extract &&
		    $fetch_cmd --git-dir=$extract/.git			\
			archive --prefix=$extract/ $rev $module |	\
			${GZIP} -c >$file
		    ;;

		mercurial)
                    ${ECHO} $fetch_cmd clone $repo $extract
                    $fetch_cmd -v -y --debug clone -U $repo $extract &&
                    $fetch_cmd -v -y --debug -R $extract archive	\
                        -r $rev -p $extract/ -t tgz $file
                    ;;

		svn)
		    ${TEST} -n "$rev" && rev="-r $rev"
		    ${ECHO} $fetch_cmd export $rev $repo $extract
		    $fetch_cmd --non-interactive --trust-server-cert	\
			export $rev $repo $extract &&
		    ${FIND} $extract \! -type d | ${SORT} | ${PAX} -wzf $file
		    ;;
	    esac
	    status=$?
	    ${TEST} -d "$extract" && rm -rf "$extract"
	    exit $status
	)
	if ${TEST} $? -ne 0; then
	    ${ECHO} 1>&2 "$self: Unable to fetch expected file $file"
	    rm -f "$outputpath"
	    continue
	fi
	if verify_file $outputpath; then
	    ${TEST} -z "$resume" || ${MV} -f $outputpath $path
	    break
	else
	    ${ECHO} 1>&2 "$self: Unable to verify fetched file $file"
	    rm -f "$outputpath"
	fi
	if ${TEST} -n "$resume"; then
	    if ${TEST} -f $path; then
		${CP} -f $path $outputpath
	    else
		${TOUCH} $outputpath
	    fi
	fi
done
if ${TEST} -f $path; then
    exit 0
else
    exit 1
fi
